/* 03-prod-cons.c

   All 'W' complete first, then 'N' objects start and complete.

   Usage: 03-prod-cons

   Note: the 'Monitor' in this case consists of procedures waitIt, endIt,
   saveIt and doIt.  A user defines a mutex and cond variable in an
   Object object.  This gets passed from function to function and is used
   to set up the critical sections in the monitor's functions.
*/
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

/* 
   A monitor has an id and mutex and cond variables 
*/
typedef struct {
   int id;
   pthread_mutex_t lock;
   pthread_cond_t notifier;
} Monitor;

/* 
   Each object associates with a monitor - there may be many that associate
   with the same monitor so there is a pointer to the associated monitor
   int the Object struct
*/
typedef struct {
   Monitor *monitor;
   int id;
} Object;

bool present[10];
void doIt (void*);

bool isEmpty () {
   int i;
   for (i=0 ; i < 10 ; i++) if (present[i]) return false;
   return true;
}

void makeEmpty () {
   int i;
   for (i=0 ; i < 10 ; i++) present[i] = false;
}

void waitIt(void *obj) {
   int id = ((Object*)obj)->id;
   if (present[id]) return; else present[id] = true;
}
   
void endIt(void *obj) {
   int id = ((Object*)obj)->id;
   if (present[id]) present[id] = false;
   if (isEmpty()) doIt(obj);
}
   
/* 
   Exactly one active thread is allowed to run in this function at a time.
   When a thread is put into a wait state with pthread_cond_wait it gives
   up its monitor_mutex lock so that another thread may enter the function.
   When a waiting thread is signaled with pthread_cond_signal or
   pthread_cond_broadcast it becomes active and no other thread will be 
   active in the function until it leaves.

   The obj argument contains a reference to a mutex and to a cond variable
   so that the same code can apply to any number of monitors and objects.
*/
void saveIt (void *obj) {
   pthread_mutex_t *lock = &(((Object*)obj)->monitor)->lock;
   pthread_cond_t *notifier = &(((Object*)obj)->monitor)->notifier;
   int id = ((Object*)obj)->id;
   int m = (((Object*)obj)->monitor)->id;

   pthread_mutex_lock(lock);
   printf("\tsaveit: wait N(%d) in monitor %d\n", id, m);
   pthread_cond_wait(notifier, lock); 
   printf("\tsaveit: release N(%d) in monitor %d\n", id, m);
   pthread_mutex_unlock(lock);
}
   
void doIt (void *obj) { 
   pthread_mutex_t *lock = &(((Object*)obj)->monitor)->lock;
   pthread_cond_t *notifier = &(((Object*)obj)->monitor)->notifier;

   printf("DoIt called\n");

   pthread_mutex_lock(lock);
   pthread_cond_broadcast(notifier);
   pthread_mutex_unlock(lock);
}

/*
  This is the function that is tied to the 'N' objects
  It chooses a mutex and cond variables depending on the monitor selected
  by the main program.
*/
void *Nrun (void *obj) {
   int id = ((Object*)obj)->id;
   int i;

   printf("Starting N(%d)\n", id);

   saveIt(obj);

   for (i=0 ; i < 10000000 ; i++) {
      if ((i%1000000) == 0) printf("\tN(%d):\t%d\n",id,i);
      fflush(stdout);
   }
   printf("Finishing N(%d)\n", id);

   pthread_exit(NULL);
}

/*
  This is the function that is tied to the 'W' objects
  It chooses a mutex and cond variables depending on the monitor selected
  by the main program.
*/
void *Wrun (void *obj) {
   int id = ((Object*)obj)->id;
   long i;

   waitIt(obj);

   printf("Starting W(%d)\n", id);
   for (i=0 ; i < 10000000 ; i++) {
      if ((i%1000000) == 0) printf("\tW(%d):\t%d\n",id,i);
      fflush(stdout);
   }
   printf("Finishing W(%d)\n", id);

   endIt(obj);

   pthread_exit(NULL);
}

/* 
  Initialize the monitor object - add an id and initialize the mutex 
  and cond variables 
*/
void init_monitor(Monitor *monitor, int id) {
   monitor->id = id;
   pthread_mutex_init(&monitor->lock, NULL);
   pthread_cond_init (&monitor->notifier, NULL);
}

void init_object(Object *obj, Monitor *mon, int id) {
   obj->monitor = mon;
   obj->id = id;
}

int main (int argc, char **argv) {
   pthread_t w1, w2, w3, w4, n1, n2, n3;
   pthread_attr_t attr;
   Object t1, t2, t3, t4, t5, t6, t7;
   Monitor m;

   /* initialize the W and N objects */
   init_monitor(&m, 1);
   init_object(&t1, &m, 1);
   init_object(&t2, &m, 2);
   init_object(&t3, &m, 3);
   init_object(&t4, &m, 4);
   init_object(&t5, &m, 5);
   init_object(&t6, &m, 6);
   init_object(&t7, &m, 7);

   /* set the attribute variable and create and run the threads */
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
   pthread_create(&w1, &attr, Wrun, (void*)&t1);
   pthread_create(&w2, &attr, Wrun, (void*)&t2);
   pthread_create(&w3, &attr, Wrun, (void*)&t3);
   pthread_create(&w4, &attr, Wrun, (void*)&t4);
   pthread_create(&n1, &attr, Nrun, (void*)&t5);
   pthread_create(&n2, &attr, Nrun, (void*)&t6);
   pthread_create(&n3, &attr, Nrun, (void*)&t7);

   pthread_exit(NULL);
}
